/**
 * @file Simulation.hh
 * @author Xiaoze Lin (linxiaoze96@gmail.com)
 * @brief The simulation class.
 * @version 0.1
 *
 * @copyright Copyright (c) 2024
 *
 */
#pragma once

#include <numa.h>
#include <omp.h>

#include <filesystem>
#include <iomanip>

#include "Event.hh"
#include "Fault.hh"
#include "FuncPattern.hh"
#include "NetlistManager.hh"
#include "Utility.hh"
#include "Value.hh"
#include "log/Log.hh"

// macros to fast access netlist info
#define type(Gid) (_gates + Gid)->gtype
#define level(Gid) (_gates + Gid)->level
#define numSuccs(Gid) (_gates + Gid)->numSuccs
#define numPreds(Gid) (_gates + Gid)->numPreds
#define preds(Gid) (_preds + (_gates + Gid)->offsetPreds)
#define succs(Gid) (_succs + (_gates + Gid)->offsetSuccs)

// macros to fast access netlist info by different thread
#define typeThread(Gid, Thread_id) (_gatesPPtr[Thread_id] + Gid)->gtype
#define levelThread(Gid, Thread_id) (_gatesPPtr[Thread_id] + Gid)->level
#define numSuccsThread(Gid, Thread_id) (_gatesPPtr[Thread_id] + Gid)->numSuccs
#define numPredsThread(Gid, Thread_id) (_gatesPPtr[Thread_id] + Gid)->numPreds
#define predsThread(Gid, Thread_id) (_predsPPtr[Thread_id] + (_gatesPPtr[Thread_id] + Gid)->offsetPreds)
#define succsThread(Gid, Thread_id) (_succsPPtr[Thread_id] + (_gatesPPtr[Thread_id] + Gid)->offsetSuccs)

#define MAX_PARALLEL_FAULTS 63
#define MAX_PARALLEL_FAULTS_256 255

namespace ifsim {

class Simulation
{
 public:
  Simulation();
  Simulation(FaultManager* fault, FuncPatternManager* func_pat);
  ~Simulation() { ; }

  void getNetlistInfo();
  void initGmvAndGetNetlistInfo();

  // good sim with func pat
  bool simGoodCktWithFuncPat(bool vec_mode);
  void assignToggledStimuli(int frame_id);
  void eventDrivenLogicSimWithFuncPat(int frame_id);
  bool comparePoValue(const std::vector<Value>& po_vals);
  bool updateGateValueWithFuncPat(int gid, int frame_id, Value new_val);
  void addSuccEvents(int gid);

  // fault sim with func pat
  bool faultSimulate(int num_thread, bool is_bit_parallel, bool vec_mode);
  bool faultSimulateWithFuncPat(std::vector<FaultClass>& fault_class_after_sim, bool is_bit_parallel, bool vec_mode);
  void initDataStructuresAndPtrForFsimWithFuncPat(bool vec_mode);
  void allocDataStructuresToNumaNode(int thread_id, int curr_node_id, bool vec_mode);
  void freeDataStructuresInNumaNode(bool vec_mode);
  void assignThreadToggledStimuli(int frame_id, int thread_id);
  void assignThreadToggledStimuliInVecMode(int frame_id, int thread_id);
  void eventDrivenFaultSimWithFuncPat(int frame_id, int thread_id, int num_faults_in_group);
  void eventDrivenFaultSimWithFuncPatInVecMode(int frame_id, int thread_id, int num_faults_in_group);
  void injectParallelFaultsByModifiedNetlist(const std::vector<Fault>& faults, int thread_id);
  void injectParallelFaultsByModifiedNetlistInVecMode(const std::vector<Fault>& faults, int thread_id);
  void removeParallelFaultsByRecoverNetlist(int thread_id);
  void removeParallelFaultsByRecoverNetlistInVecMode(int thread_id);
  void compareParallelFsimPoVal(std::vector<FaultClass>& is_detected, int frame_id, int thread_id, int num_faults_in_group);
  void compareParallelFsimPoValInVecMode(std::vector<FaultClass>& is_detected, int frame_id, int num_faults_in_group, int thread_id);
  void resetOriSimVal(int thread_id);
  void resetOriSimValInVecMode(int thread_id);
  bool updateThreadGateValueInFsim(int gid, int thread_id, Value new_val);
  bool updateThreadGateValue256(int gid, int thread_id, Value256 new_val);
  bool updateThreadGateValue256InFsim(int gid, int thread_id, Value256 new_val);
  void addThreadSuccEvents(int gid, int thread_id);
  void dumpFaultSimInfo(int num_det_sim_fault, int num_pot_fault, double collapsed_time, bool is_read_fault_list);

  // event-driven logic sim
  void evalGateBasedOnCurrTimeFrame(int gid, uint64_t& val0, uint64_t& val1);
  void evalGateForThreadFsim(int gid, int thread_id, int num_faults_in_group, uint64_t& val0, uint64_t& val1);
  void evalGateForFsimInVecMode(int gid, int thread_id, int num_faults_in_group, __m256i_wrapper& val0, __m256i_wrapper& val1);

  void writeFault(std::vector<std::string>& str_vec, Fault curr_fault);
  std::string mask64ToBinary(uint64_t val);

 private:
  // sim relevant
  QueueBasedEventManager* _eventManager;  // event manager pointer
  std::vector<Value> _gmv;                // good machine value
  std::vector<Value> _dffMasterVal;       // sim val for dff master
  Value* _gmvPtr;
  Value** _gmvPPtr;
  Value* _dffMasterValPtr;
  Value** _dffMasterValPPtr;
  int _numThread;

  // vec mode relevant
  std::vector<Value256> _gmv256;           // good machine value in vec mode
  std::vector<Value256> _dffMasterVal256;  // sim val for dff master in vec mode
  Value256** _gmv256PPtr;
  Value256** _dffMasterVal256PPtr;
  const __m256i _allOnes256 = _mm256_set1_epi64x(ALL_ONES_64);

  // fault sim relevant
  std::vector<int> _threadModifiedNumNodes;  // size of modified netlist for each thread
  std::vector<int> _threadCurrPredsVecSize;  // size of modified pred vec for each thread
  std::vector<int> _threadCurrSuccsVecSize;  // size of modified succ vec for each thread
  Value** _storedGmvPPtr;
  Value256** _storedGmv256PPtr;
  int** _changedGmvIdPPtr;
  char** _isValChangedPPtr;
  int** _masterChangedGmvIdPPtr;
  char** _masterIsValChangedPPtr;
  int** _needComparePoIdsPPtr;
  char** _isPoNeedComparedPPtr;
  int* _threadModifiedNumNodesPtr;
  int* _threadCurrPredsVecSizePtr;
  int* _threadCurrSuccsVecSizePtr;
  int _oriPredsVecSize;
  int _oriSuccsVecSize;
  bool _simBuiltFault;

  // sim netlist relevant
  int _numNodes;
  int _numLevels;
  int _numPIs;
  int _numPOs;
  int _numDffs;
  int _numClocks;
  int _firstPO;
  int _firstPI;
  int _firstDffId;
  const SimGate* _simGatesPtr;
  OclGate* _gates;
  int* _preds;
  int* _succs;
  OclGate** _gatesPPtr;
  int** _predsPPtr;
  int** _succsPPtr;

  NetlistManager* _netlistManager;
  FaultManager* _faultManager;
  FuncPatternManager* _funcPatManager;
};
}  // namespace ifsim